// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library stream_listen_zeno_test;

import "dart:async";
import "package:expect/async_helper.dart";
import "package:expect/expect.dart";

main() {
  asyncStart();
  var controller;
  for (bool overrideDone in [false, true]) {
    for (bool sync in [false, true]) {
      var mode = "${sync ? "-sync" : ""}${overrideDone ? "-od" : ""}";
      controller = new StreamController(sync: sync);
      testStream("SC$mode", controller, controller.stream, overrideDone);
      controller = new StreamController.broadcast(sync: sync);
      testStream("BSC$mode", controller, controller.stream, overrideDone);
      controller = new StreamController(sync: sync);
      testStream(
        "SCAB$mode",
        controller,
        controller.stream.asBroadcastStream(),
        overrideDone,
        3,
      );
      controller = new StreamController(sync: sync);
      testStream(
        "SCMap$mode",
        controller,
        controller.stream.map((x) => x),
        overrideDone,
        3,
      );
    }
  }
  asyncEnd();
}

void testStream(
  String name,
  StreamController controller,
  Stream stream,
  bool overrideDone, [
  int registerExpect = 0,
]) {
  asyncStart();
  late StreamSubscription sub;
  late Zone zone;
  int registerCount = 0;
  int callbackBits = 0;
  int stepCount = 0;
  late void Function() step;
  void nextStep() {
    Zone.root.scheduleMicrotask(step);
  }

  runZoned(
    () {
      zone = Zone.current;
      sub = stream.listen(
        (v) {
          Expect.identical(zone, Zone.current, name);
          Expect.equals(42, v, name);
          callbackBits |= 1;
          nextStep();
        },
        onError: (e, s) {
          Expect.identical(zone, Zone.current, name);
          Expect.equals("ERROR", e, name);
          callbackBits |= 2;
          nextStep();
        },
        onDone: () {
          Expect.identical(zone, Zone.current, name);
          if (overrideDone) throw "RUNNING WRONG ONDONE";
          callbackBits |= 4;
          nextStep();
        },
      );
      registerExpect += 3;
      Expect.equals(registerExpect, registerCount, name);
    },
    zoneSpecification: new ZoneSpecification(
      registerCallback: <R>(self, p, z, R callback()) {
        Expect.identical(zone, self, name);
        registerCount++;
        return () {
          Expect.identical(zone, Zone.current, name);
          return callback();
        };
      },
      registerUnaryCallback: <R, T>(self, p, z, R callback(T a)) {
        Expect.identical(zone, self, name);
        registerCount++;
        return (a) {
          Expect.identical(zone, Zone.current, name);
          return callback(a);
        };
      },
      registerBinaryCallback:
          <R, T1, T2>(self, package, z, R callback(T1 a, T2 b)) {
            Expect.identical(zone, self, name);
            registerCount++;
            return (a, b) {
              Expect.identical(zone, Zone.current, name);
              return callback(a, b);
            };
          },
    ),
  );

  int expectedBits = 0;
  step = () {
    var stepName = "$name-$stepCount";
    Expect.identical(Zone.root, Zone.current, stepName);
    Expect.equals(expectedBits, callbackBits, stepName);
    switch (stepCount++) {
      case 0:
        expectedBits |= 1;
        controller.add(42);
        break;
      case 1:
        expectedBits |= 2;
        controller.addError("ERROR", null);
        break;
      case 2:
        Expect.equals(registerExpect, registerCount, stepName);
        sub.onData((v) {
          Expect.identical(zone, Zone.current, stepName);
          Expect.equals(37, v);
          callbackBits |= 8;
          nextStep();
        });
        Expect.equals(++registerExpect, registerCount, stepName);
        expectedBits |= 8;
        controller.add(37);
        break;
      case 3:
        Expect.equals(registerExpect, registerCount, stepName);
        sub.onError((e, s) {
          Expect.identical(zone, Zone.current);
          Expect.equals("BAD", e);
          callbackBits |= 16;
          nextStep();
        });
        Expect.equals(++registerExpect, registerCount, stepName);
        expectedBits |= 16;
        controller.addError("BAD", null);
        break;
      case 4:
        Expect.equals(registerExpect, registerCount, stepName);
        if (overrideDone) {
          sub.onDone(() {
            Expect.identical(zone, Zone.current);
            callbackBits |= 32;
            nextStep();
          });
          registerExpect++;
          expectedBits |= 32;
        } else {
          expectedBits |= 4;
        }
        Expect.equals(registerExpect, registerCount, stepName);
        controller.close();
        break;
      case 5:
        asyncEnd();
    }
  };
  step();
}
